<?php
namespace remote\curl;

use GamesBundle\Services\ContainerAware;

/**
 * Curl模块
 * 连贯操作：$object->setReferer($string)->setAgent(string)->setCookies($array, $mode = 'covered')->setPost($array)->request($url);
 * @version 1.0
 * @author lzp<mail@lzp.name>
 * @copyright
 */

class Curl extends ContainerAware
{

    private $header;              //返回的header信息

    private $httpcode;            //收到的HTTP代码
    private $post = array();      //设置的POST参数，以数组的形式保存
    private $cookies = array();   //上一次响应设置的Cookies；或设置的COOKIES，以数组的形式保存
    private $agent;               //设置的User-Agent
    private $referer;             //设置的Referer

    /**
     * 捕获$this->url的页面，并保存cookie至$this->cookies
     * @version 1.1  更新了cookie记录机制，后一次访问cookie不会覆盖前一次访问的cookie
     * @return string 获取到的页面String
     * @throws Exception
     * @author lzp<mail@lzp.name>
     * @copyright
     */
    public function request($url)
    {
        $ch = curl_init();        //初始化CURL
        curl_setopt($ch, CURLOPT_URL, $url);        //设置URL
        if(!empty($this->post)){                    //设置POST数据
            curl_setopt($ch,CURLOPT_POST,1);
            curl_setopt($ch,CURLOPT_POSTFIELDS,$this->get_post());
        }
        if(!empty($this->referer)){                     //设置Referer
            curl_setopt($ch, CURLOPT_REFERER, $this->referer);
        }
        if(!empty($this->agent)){                       //设置User-Agent
            curl_setopt($ch, CURLOPT_USERAGENT, $this->agent);
        }
        if(!empty($this->cookies)){                     //设置Cookie
            curl_setopt($ch, CURLOPT_COOKIE, $this->get_cookies());
        }

        curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 3);    //设置最长连接时间3秒
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);          //超时时间30秒
        curl_setopt($ch, CURLOPT_HEADER, 1);            //获取头部信息
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); //返回原生的（Raw）输出
        //curl_setopt($ch, CURLOPT_AUTOREFERER, true);  //自动重定向

        $content = @curl_exec($ch);     // 执行并获取返回结果

        if (curl_errno($ch)) {
            throw new Exception('Error Processing Request:cURL('.curl_errno($ch).')', 1);
        }
        $this->httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);

        curl_close($ch);                // 关闭CURL

        list($this->header, $html) = explode("\r\n\r\n", $content, 2);    // 解析HTTP数据流

        $this->setCookies($this->fetchCookie());    // 解析COOKIE

        return $html;
    }

    /**
     * 设置捕获页面时发送的POST数据
     * @version 1.0
     * @param array $post 捕获页面时将发送$post
     * @return object 返回一个Http对象，用于连贯操作
     * @author lzp<mail@lzp.name>
     * @copyright
     */
    public function setPost($post)
    {
        $this->post = is_array($post) ? $post : array();
        return $this;
    }

    /**
     * 设置捕获页面时发送的Cookies：
     * 参数$mode有以下3种模式，其他非法字符按默认执行
     * 'append'(默认):将新的$cookies附加到上一次响应设置的Cookies上，如有冲突，指定的$cookie不会覆盖原有的Cookies；
     * 'covered':用新的$cookies覆盖到上一次响应设置的Cookies上，如有冲突，指定的$cookie将会覆盖原有的Cookies；
     * 'new':废弃上一次响应设置的Cookies，仅使用指定的$cookie；
     * 页面默认使用上一次响应设置的Cookies进行新的请求，如果强制不使用Cookie，可以使用setCookies(NULL, 'new')
     * @version 1.0
     * @param array $cookies 设置$cookie
     * @param string $mode 设置$cookie的添加模式
     * @return object 返回一个Http对象，用于连贯操作
     * @author lzp<mail@lzp.name>
     * @copyright
     */
    public function setCookies($cookies, $mode='covered')
    {
        !is_array($cookies) && $cookies = array();
        switch ($mode) {
            case 'new':
                $this->cookies = $cookies;
                break;
            case 'append':
                $this->cookies = array_merge($cookies, $this->cookies);
                break;
            case 'covered':
            default:
                $this->cookies = array_merge($this->cookies, (array)$cookies);
        }
        return $this;
    }

    /**
     * 设置捕获页面时发送的User-Agent
     * @version 1.0
     * @param string $agent 将User-Agent设置为$agent
     * @return object 返回一个Http对象，用于连贯操作
     * @author lzp<mail@lzp.name>
     * @copyright
     */
    public function setAgent($agent)
    {
        is_string($agent) && $this->agent = $agent;
        return $this;
    }

    /**
     * 设置捕获页面时发送的Referer
     * @version 1.0
     * @param string $referer 将Referer设置为$referer
     * @return object 返回一个Http对象，用于连贯操作
     * @author lzp<mail@lzp.name>
     * @copyright
     */
    public function setReferer($referer)
    {
        is_string($referer) && $this->referer = $referer;
        return $this;
    }

    public function fetchCookie()
    {
        if (preg_match_all("/set\-cookie:([^;]*)/i", $this->header, $matches)) {    // 解析COOKIE
            return $this->_kvtoarray('; ',implode('; ', $matches[1]));
        }
    }

    public function fetchStatus()
    {
        return $this->httpcode;
    }

    private function get_post()
    {
        return $this->_arraytokv('&', $this->post);
    }

    private function get_cookies()
    {
        return $this->_arraytokv('; ', $this->cookies);
    }

    /**
     * 将字符串转换成数组，按照$separator为分隔符分离出每个包含键值对的字符串，在每个包含键值对的字符串中以$contexid（默认为'='）为关联表示符分离键与值
     * @version 1.0
     * @param string $separator 按照$separator为分隔符分离出每个包含键值对的字符串
     * @param string $string 要转换成数组的字符串，转换后为一位数组
     * @param string $contexid 在每个包含键值对的字符串中以$contexid（默认为'='）为关联表示符分离键与值
     * @return string 返回转换后的字符串
     * @author lzp<mail@lzp.name>
     * @copyright
     */
    private function _kvtoarray($separator, $string, $contexid = '=')
    {
        $array = array();
        foreach (explode($separator, $string) as $value) {
            list($key, $val) = explode('=', $value);
            $array[$key] = $val;
        }
        return $array;
    }

    /**
     * 将数组转换成字符串，在每个键与值之间使用$contexid（默认为'='）连接，各键值对之间用$separator连接
     * @version 1.0
     * @param string $separator 各键值对之间用$separator连接
     * @param array $array 要转换成字符串的数组，支持递归，但递归后不可逆向转换为原始的多为数组
     * @param string $contexid 在每个键与值之间使用$contexid（默认为'='）连接
     * @return string 返回转换后的字符串
     * @author lzp<mail@lzp.name>
     * @copyright
     */
    private function _arraytokv($separator, &$array, $contexid = '=')
    {
        $string = '';
        $append = false;
        foreach ($array as $key => $val) {
            $append && $string .= $separator;
            if (is_array($val)) {
                $string .= $this->_arraytokv($separator, $val);
            } else {
                $string .= $key.$contexid.$val;
                $append = true;
            }
        }
        return $string;
    }

}